using System.Data;
using TPXSDK.NET.Share;

namespace TPXSDK.NET.Log;

/// <summary>
/// 常规日志解析器(每个日志文件对应一个Parser)
/// </summary>
public class LogParserGeneric : LogParserBase
{
    #region 接口实现
    public override void DoLoad(string fileName)
    {
        ErrorCount = 0;
        Task task = new(action: DoLoadImp, fileName);
        task.Start();
    }

    /// <summary>
    /// 启动日志数据加载分析
    /// </summary>
    /// <param name="fileName">日志文件名称</param>
    public void DoLoadImp(object? fileName)
    {
        FilePath = fileName as string;
        if (null == FilePath || !File.Exists(FilePath)) return;
        long pos;

        if (FilePath.Contains("param", StringComparison.CurrentCultureIgnoreCase))
        {
            using var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
            LoadParam(fs);
            WorkDone();
            return;
        }

        // try to get 3 line from log file
        var strHeaderLines = new string[3];
        using (var sr = new StreamReader(FilePath))
        {
            var l0 = sr.ReadLine();
            var l1 = sr.ReadLine();
            var l2 = sr.ReadLine();

            if (string.IsNullOrEmpty(l0) ||
                string.IsNullOrEmpty(l1) ||
                string.IsNullOrEmpty(l2))
            {
                return;
            }
            pos  = l0.Length + 2;
            pos += l1.Length + 2;
            pos += l2.Length + 2;
            strHeaderLines[0] = l0;
            strHeaderLines[1] = l1;
            strHeaderLines[2] = l2;
        }

        using (var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read))
        {
            if (fs.Length < pos) return;
            fs.Position = pos;
            LoadData(strHeaderLines, fs);
        }
    }
    #endregion

    /// <summary>
    /// 加载参数日志文件
    /// </summary>
    private void LoadParam(Stream fs)
    {
        using var sr = new StreamReader(fs);
        Data = new DataTable("Param");
        Data.Columns.Add("NAME");
        Data.Columns.Add("VALUE");

        var strSig = sr.ReadLine();
        if (!string.IsNullOrEmpty(strSig))
        {
            var dataRow = Data.NewRow();
            dataRow[0] = "Signature";
            dataRow[1] = strSig;
            Data.Rows.Add(dataRow);
            Report(Data.Rows.Count);
        }

        var strDevType = sr.ReadLine();
        if (!string.IsNullOrEmpty(strDevType))
        {
            var dataRow = Data.NewRow();
            dataRow[0] = "DevType";
            dataRow[1] = strDevType;
            Data.Rows.Add(dataRow);
            Report(Data.Rows.Count);
        }

        while (!sr.EndOfStream)
        {
            var line = sr.ReadLine();
            if (string.IsNullOrEmpty(line)) continue;
            var idx = line.IndexOf('=');
            if (idx < 0) continue;
            // 解析参数名称和值
            var strName = line[..idx];
            var strVal = line[(idx + 1)..];

            var dataRow = Data.NewRow();
            dataRow[0] = strName;
            dataRow[1] = strVal;

            Data.Rows.Add(dataRow);
            Report(Data.Rows.Count);
        }
    }

    /// <summary>
    /// 加载日志数据文件
    /// </summary>
    /// <param name="fmtStr">日志解析描述</param>
    /// <param name="fs">日志文件流</param>
    private void LoadData(string[] fmtStr, Stream fs)
    {
        // TRY Load by data format string and column name
        var cols = fmtStr[1].Split(' ');// 列名称
        if (cols.Length != fmtStr[2].Length) return;// 列数据类型

        Data = new DataTable(fmtStr[0]); //Log FileName
        foreach (var c in cols)
        {
            Data.Columns.Add(c);
        }

        var entryLen = GetEntryLen(fmtStr[2]);
        var entryCnt = (fs.Length - fs.Position) / entryLen;
        var entryData = new byte[entryLen];
        while (fs.Length > fs.Position)
        {
            fs.Read(entryData, 0, entryLen);
            var crcExp = CRC16.Calc(entryData, 0, entryLen - 2);
            var crcReal = DataUtils.GEN_WORD(entryData[entryLen - 2], entryData[entryLen - 1]);
            if (crcExp != crcReal)
            {
                ErrorCount++;
                continue;
            }
            var dr = Data.NewRow();
            GenRowData(entryData, fmtStr[2], dr);
            Data.Rows.Add(dr);
            Report((int)(Data.Rows.Count * 100 / entryCnt));
        }

        WorkDone();
    }

    /*
      b   : int8_t
      B   : uint8_t
      h   : int16_t
      H   : uint16_t
      i   : int32_t
      I   : uint32_t
      f   : float
      d   : double
      q   : int64_t
      Q   : uint64_t
     */
    private static void GenRowData(byte[] entryData, string fmtStr, DataRow dr)
    {
        var offset = 0;
        for (var i = 0; i < fmtStr.Length; i++)
        {
            var c = fmtStr[i];
            if (c == 'b') dr[i] = (sbyte)entryData[offset++];
            if (c == 'B') dr[i] = entryData[offset++];
            if (c == 'h')
            {
                dr[i] = BitConverter.ToInt16(entryData, offset);
                offset += 2;
            }

            if (c == 'H')
            {
                dr[i] = BitConverter.ToUInt16(entryData, offset);
                offset += 2;
            }

            if (c == 'i')
            {
                dr[i] = BitConverter.ToInt32(entryData, offset);
                offset += 4;
            }

            if (c == 'I')
            {
                dr[i] = BitConverter.ToUInt32(entryData, offset);
                offset += 4;
            }

            if (c == 'f')
            {
                dr[i] = BitConverter.ToSingle(entryData, offset);
                offset += 4;
            }

            if (c == 'd')
            {
                dr[i] = BitConverter.ToDouble(entryData, offset);
                offset += 8;
            }

            if (c == 'q')
            {
                dr[i] = BitConverter.ToInt64(entryData, offset);
                offset += 8;
            }

            if (c == 'Q')
            {
                dr[i] = BitConverter.ToUInt64(entryData, offset);
                offset += 8;
            }
        }
    }

    private static int GetEntryLen(string fmt)
    {
        var len = 0;
        foreach (var c in fmt)
        {
            if (c == 'b' || c == 'B') len += 1;
            if (c == 'h' || c == 'H') len += 2;
            if (c == 'i' || c == 'I' || c == 'f') len += 4;
            if (c == 'd' || c == 'q' || c == 'Q') len += 8;
        }
        return len;
    }
}//end LogParser
